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, itemCount: itemCount,
onCancel: onCancel, onCancel: onCancel,
onDone: (processed) { onDone: (processed) {
Navigator.of(context).pop(); Navigator.pop(context);
onDone?.call(processed); 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/info_page.dart';
import 'package:aves/widgets/viewer/info/notifications.dart'; import 'package:aves/widgets/viewer/info/notifications.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:screen_brightness/screen_brightness.dart'; import 'package:screen_brightness/screen_brightness.dart';
class ViewerVerticalPageView extends StatefulWidget { class ViewerVerticalPageView extends StatefulWidget {
@ -93,18 +94,7 @@ class _ViewerVerticalPageViewState extends State<ViewerVerticalPageView> {
// fake page for opacity transition between collection and viewer // fake page for opacity transition between collection and viewer
const transitionPage = SizedBox(); const transitionPage = SizedBox();
final imagePage = hasCollection final imagePage = _buildImagePage();
? MultiEntryScroller(
collection: collection!,
pageController: widget.horizontalPager,
onPageChanged: widget.onHorizontalPageChanged,
onViewDisposed: widget.onViewDisposed,
)
: entry != null
? SingleEntryScroller(
entry: entry!,
)
: const SizedBox();
final infoPage = NotificationListener<ShowImageNotification>( final infoPage = NotificationListener<ShowImageNotification>(
onNotification: (notification) { 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() { void _onVerticalPageControllerChanged() {
final page = widget.verticalPager.page!; 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,12 +198,15 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
_goToCollection(notification.filter); _goToCollection(notification.filter);
} else if (notification is EntryRemovedNotification) { } else if (notification is EntryRemovedNotification) {
_onEntryRemoved(context, notification.entry); _onEntryRemoved(context, notification.entry);
} } else if (notification is ToggleOverlayNotification) {
return false;
},
child: NotificationListener<ToggleOverlayNotification>(
onNotification: (notification) {
_overlayVisible.value = notification.visible ?? !_overlayVisible.value; _overlayVisible.value = notification.visible ?? !_overlayVisible.value;
} 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; return true;
}, },
child: Stack( child: Stack(
@ -226,7 +229,6 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
), ),
), ),
), ),
),
); );
} }
@ -249,18 +251,12 @@ class _EntryViewerStackState extends State<EntryViewerStack> with FeedbackMixin,
); );
} }
return NotificationListener<ShowInfoNotification>( return mainEntry.isMultiPage
onNotification: (notification) {
_goToVerticalPage(infoPage);
return true;
},
child: mainEntry.isMultiPage
? PageEntryBuilder( ? PageEntryBuilder(
multiPageController: context.read<MultiPageConductor>().getController(mainEntry), multiPageController: context.read<MultiPageConductor>().getController(mainEntry),
builder: (pageEntry) => _buildContent(pageEntry: pageEntry), builder: (pageEntry) => _buildContent(pageEntry: pageEntry),
) )
: _buildContent(), : _buildContent();
);
}, },
); );