viewer: keyboard shortcuts for navigation

This commit is contained in:
Thibault Deckers 2022-02-24 18:00:09 +09:00
parent 365ddc0f92
commit fc1234ca63
3 changed files with 106 additions and 50 deletions

View file

@ -84,7 +84,7 @@ mixin FeedbackMixin {
itemCount: itemCount,
onCancel: onCancel,
onDone: (processed) {
Navigator.of(context).pop();
Navigator.pop(context);
onDone?.call(processed);
},
),

View file

@ -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<ViewerVerticalPageView> {
// 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<ShowImageNotification>(
onNotification: (notification) {
@ -150,6 +140,58 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
);
}
Widget _buildImagePage() {
Widget? child;
Map<ShortcutActivator, Intent>? 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<Intent>(onInvoke: (intent) => _jumpHorizontalPage(-1)),
ShowNextIntent: CallbackAction<Intent>(onInvoke: (intent) => _jumpHorizontalPage(1)),
LeaveIntent: CallbackAction<Intent>(onInvoke: (intent) => Navigator.pop(context)),
ShowInfoIntent: CallbackAction<Intent>(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<ViewerVerticalPageView> {
}
}
}
// 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();
}

View file

@ -198,32 +198,34 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
_goToCollection(notification.filter);
} else if (notification is EntryRemovedNotification) {
_onEntryRemoved(context, notification.entry);
}
return false;
},
child: NotificationListener<ToggleOverlayNotification>(
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<EntryViewerStack> with FeedbackMixin,
);
}
return NotificationListener<ShowInfoNotification>(
onNotification: (notification) {
_goToVerticalPage(infoPage);
return true;
},
child: mainEntry.isMultiPage
? PageEntryBuilder(
multiPageController: context.read<MultiPageConductor>().getController(mainEntry),
builder: (pageEntry) => _buildContent(pageEntry: pageEntry),
)
: _buildContent(),
);
return mainEntry.isMultiPage
? PageEntryBuilder(
multiPageController: context.read<MultiPageConductor>().getController(mainEntry),
builder: (pageEntry) => _buildContent(pageEntry: pageEntry),
)
: _buildContent();
},
);