feat: add keyboard shortcuts and Tab link traversal on pointer hosts
This commit is contained in:
parent
fa7aa5557c
commit
56db479df0
6 changed files with 798 additions and 272 deletions
|
|
@ -1,5 +1,8 @@
|
|||
import 'package:flutter/foundation.dart'
|
||||
show defaultTargetPlatform, TargetPlatform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show ScrollDirection;
|
||||
import 'package:flutter/services.dart' show LogicalKeyboardKey;
|
||||
import 'package:signals/signals_flutter.dart' show SignalBuilder;
|
||||
|
||||
import '../../application/controllers/browser_controller.dart'
|
||||
|
|
@ -32,13 +35,22 @@ class ReaderScreen extends StatefulWidget {
|
|||
|
||||
class _ReaderScreenState extends State<ReaderScreen> {
|
||||
final _scroll = ScrollController();
|
||||
final _addressFocus = FocusNode();
|
||||
|
||||
/// The address bar is on screen from the start, hidden while the reader
|
||||
/// scrolls down, back on any upward scroll.
|
||||
/// scrolls down, back on any upward scroll. A pointer host with a keyboard
|
||||
/// has room to spare and keys to reach the bar, so it never hides.
|
||||
bool _chromeVisible = true;
|
||||
bool _showBrokenText = false;
|
||||
final _checkedSubscriptions = <String>{};
|
||||
|
||||
/// Touch devices navigate with gestures; the others are pointer hosts
|
||||
/// whose readers reach the chrome with the mouse and the keyboard.
|
||||
static bool get _isTouch => switch (defaultTargetPlatform) {
|
||||
TargetPlatform.android || TargetPlatform.iOS => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -49,10 +61,14 @@ class _ReaderScreenState extends State<ReaderScreen> {
|
|||
void dispose() {
|
||||
_scroll.removeListener(_onScroll);
|
||||
_scroll.dispose();
|
||||
_addressFocus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
// On a pointer host the bar stays on screen, so scrolling changes
|
||||
// nothing about it.
|
||||
if (!_isTouch) return;
|
||||
if (!_scroll.hasClients) return;
|
||||
final position = _scroll.position;
|
||||
|
||||
|
|
@ -70,7 +86,7 @@ class _ReaderScreenState extends State<ReaderScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final deps = AppScope.of(context).deps;
|
||||
|
||||
return Scaffold(
|
||||
final reader = Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
AnimatedSlide(
|
||||
|
|
@ -82,7 +98,7 @@ class _ReaderScreenState extends State<ReaderScreen> {
|
|||
curve: Curves.easeOut,
|
||||
alignment: Alignment.topCenter,
|
||||
child: _chromeVisible
|
||||
? const AddressBar()
|
||||
? AddressBar(focusNode: _addressFocus)
|
||||
: const SizedBox(width: double.infinity),
|
||||
),
|
||||
),
|
||||
|
|
@ -134,6 +150,43 @@ class _ReaderScreenState extends State<ReaderScreen> {
|
|||
],
|
||||
),
|
||||
);
|
||||
|
||||
// A pointer host drives the reader from the keyboard as well: the bar
|
||||
// takes an address (Ctrl or Cmd+L, which selects what is in it), the page
|
||||
// reloads (Ctrl or Cmd+R), and the walk goes back and forward
|
||||
// (Alt+Left/Right, or Cmd+Left/Right and Cmd+[ and Cmd+] on a Mac).
|
||||
// Touch hosts navigate with gestures instead.
|
||||
if (_isTouch) return reader;
|
||||
return CallbackShortcuts(
|
||||
bindings: {
|
||||
const SingleActivator(LogicalKeyboardKey.keyL, control: true):
|
||||
_addressFocus.requestFocus,
|
||||
const SingleActivator(LogicalKeyboardKey.keyL, meta: true):
|
||||
_addressFocus.requestFocus,
|
||||
const SingleActivator(LogicalKeyboardKey.keyR, control: true):
|
||||
deps.browser.reload,
|
||||
const SingleActivator(LogicalKeyboardKey.keyR, meta: true):
|
||||
deps.browser.reload,
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true):
|
||||
deps.browser.back,
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true):
|
||||
deps.browser.forward,
|
||||
// The same walk, with the keys a Mac reader reaches for.
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true):
|
||||
deps.browser.back,
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true):
|
||||
deps.browser.forward,
|
||||
const SingleActivator(LogicalKeyboardKey.bracketLeft, meta: true):
|
||||
deps.browser.back,
|
||||
const SingleActivator(LogicalKeyboardKey.bracketRight, meta: true):
|
||||
deps.browser.forward,
|
||||
},
|
||||
// Key events reach only the focused branch, and a session starts with
|
||||
// nothing focused; this node gives them a place to arrive. Tapping the
|
||||
// address field or a control moves the focus deeper in, where the
|
||||
// shortcuts still apply.
|
||||
child: Focus(autofocus: true, skipTraversal: true, child: reader),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _body(BuildContext context, MewsPage? page) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue