viewer: keyboard shortcuts for navigation
This commit is contained in:
parent
365ddc0f92
commit
fc1234ca63
3 changed files with 106 additions and 50 deletions
|
@ -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);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue