diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index b3ab4343d..6a95d0499 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -84,7 +84,7 @@ mixin FeedbackMixin { itemCount: itemCount, onCancel: onCancel, onDone: (processed) { - Navigator.of(context).pop(); + Navigator.pop(context); onDone?.call(processed); }, ), diff --git a/lib/widgets/viewer/entry_vertical_pager.dart b/lib/widgets/viewer/entry_vertical_pager.dart index 19897bfab..72cebee21 100644 --- a/lib/widgets/viewer/entry_vertical_pager.dart +++ b/lib/widgets/viewer/entry_vertical_pager.dart @@ -11,6 +11,7 @@ import 'package:aves/widgets/viewer/entry_horizontal_pager.dart'; import 'package:aves/widgets/viewer/info/info_page.dart'; import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:screen_brightness/screen_brightness.dart'; class ViewerVerticalPageView extends StatefulWidget { @@ -93,18 +94,7 @@ class _ViewerVerticalPageViewState extends State { // fake page for opacity transition between collection and viewer const transitionPage = SizedBox(); - final imagePage = hasCollection - ? MultiEntryScroller( - collection: collection!, - pageController: widget.horizontalPager, - onPageChanged: widget.onHorizontalPageChanged, - onViewDisposed: widget.onViewDisposed, - ) - : entry != null - ? SingleEntryScroller( - entry: entry!, - ) - : const SizedBox(); + final imagePage = _buildImagePage(); final infoPage = NotificationListener( onNotification: (notification) { @@ -150,6 +140,58 @@ class _ViewerVerticalPageViewState extends State { ); } + Widget _buildImagePage() { + Widget? child; + Map? shortcuts; + + if (hasCollection) { + child = MultiEntryScroller( + collection: collection!, + pageController: widget.horizontalPager, + onPageChanged: widget.onHorizontalPageChanged, + onViewDisposed: widget.onViewDisposed, + ); + shortcuts = const { + SingleActivator(LogicalKeyboardKey.arrowLeft): ShowPreviousIntent(), + SingleActivator(LogicalKeyboardKey.arrowRight): ShowNextIntent(), + SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(), + SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(), + }; + } else if (entry != null) { + child = SingleEntryScroller( + entry: entry!, + ); + shortcuts = const { + SingleActivator(LogicalKeyboardKey.arrowUp): LeaveIntent(), + SingleActivator(LogicalKeyboardKey.arrowDown): ShowInfoIntent(), + }; + } + if (child != null) { + return FocusableActionDetector( + autofocus: true, + shortcuts: shortcuts, + actions: { + ShowPreviousIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(-1)), + ShowNextIntent: CallbackAction(onInvoke: (intent) => _jumpHorizontalPage(1)), + LeaveIntent: CallbackAction(onInvoke: (intent) => Navigator.pop(context)), + ShowInfoIntent: CallbackAction(onInvoke: (intent) => ShowInfoNotification().dispatch(context)), + }, + child: child, + ); + } + return const SizedBox(); + } + + void _jumpHorizontalPage(int delta) { + final pageController = widget.horizontalPager; + final page = pageController.page?.round(); + final _collection = collection; + if (page != null && _collection != null) { + final target = (page + delta).clamp(0, _collection.entryCount - 1); + pageController.jumpToPage(target); + } + } + void _onVerticalPageControllerChanged() { final page = widget.verticalPager.page!; @@ -205,3 +247,21 @@ class _ViewerVerticalPageViewState extends State { } } } + +// keyboard shortcut intents + +class ShowPreviousIntent extends Intent { + const ShowPreviousIntent(); +} + +class ShowNextIntent extends Intent { + const ShowNextIntent(); +} + +class LeaveIntent extends Intent { + const LeaveIntent(); +} + +class ShowInfoIntent extends Intent { + const ShowInfoIntent(); +} diff --git a/lib/widgets/viewer/entry_viewer_stack.dart b/lib/widgets/viewer/entry_viewer_stack.dart index d1d4e28f6..5414a37c8 100644 --- a/lib/widgets/viewer/entry_viewer_stack.dart +++ b/lib/widgets/viewer/entry_viewer_stack.dart @@ -198,32 +198,34 @@ class _EntryViewerStackState extends State with FeedbackMixin, _goToCollection(notification.filter); } else if (notification is EntryRemovedNotification) { _onEntryRemoved(context, notification.entry); - } - return false; - }, - child: NotificationListener( - onNotification: (notification) { + } else if (notification is ToggleOverlayNotification) { _overlayVisible.value = notification.visible ?? !_overlayVisible.value; - return true; - }, - child: Stack( - children: [ - ViewerVerticalPageView( - collection: collection, - entryNotifier: _entryNotifier, - verticalPager: _verticalPager, - horizontalPager: _horizontalPager, - onVerticalPageChanged: _onVerticalPageChanged, - onHorizontalPageChanged: _onHorizontalPageChanged, - onImagePageRequested: () => _goToVerticalPage(imagePage), - onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry), - ), - _buildTopOverlay(), - _buildBottomOverlay(), - const SideGestureAreaProtector(), - const BottomGestureAreaProtector(), - ], - ), + } else if (notification is ShowInfoNotification) { + // remove focus, if any, to prevent viewer shortcuts activation from the Info page + FocusManager.instance.primaryFocus?.unfocus(); + _goToVerticalPage(infoPage); + } else { + return false; + } + return true; + }, + child: Stack( + children: [ + ViewerVerticalPageView( + collection: collection, + entryNotifier: _entryNotifier, + verticalPager: _verticalPager, + horizontalPager: _horizontalPager, + onVerticalPageChanged: _onVerticalPageChanged, + onHorizontalPageChanged: _onHorizontalPageChanged, + onImagePageRequested: () => _goToVerticalPage(imagePage), + onViewDisposed: (mainEntry, pageEntry) => viewStateConductor.reset(pageEntry ?? mainEntry), + ), + _buildTopOverlay(), + _buildBottomOverlay(), + const SideGestureAreaProtector(), + const BottomGestureAreaProtector(), + ], ), ), ), @@ -249,18 +251,12 @@ class _EntryViewerStackState extends State with FeedbackMixin, ); } - return NotificationListener( - onNotification: (notification) { - _goToVerticalPage(infoPage); - return true; - }, - child: mainEntry.isMultiPage - ? PageEntryBuilder( - multiPageController: context.read().getController(mainEntry), - builder: (pageEntry) => _buildContent(pageEntry: pageEntry), - ) - : _buildContent(), - ); + return mainEntry.isMultiPage + ? PageEntryBuilder( + multiPageController: context.read().getController(mainEntry), + builder: (pageEntry) => _buildContent(pageEntry: pageEntry), + ) + : _buildContent(); }, );