feat: add D-pad navigation for phones

This commit is contained in:
randogoth 2026-10-13 11:47:01 +03:00
parent 414f8e8484
commit b3df7fba9f
6 changed files with 380 additions and 31 deletions

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -37,6 +38,8 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
final _screenFocus = FocusNode();
final _sourceScroll = ScrollController();
final _previewScroll = ScrollController();
Timer? _centerHold;
final _menuKey = GlobalKey<PopupMenuButtonState<String>>();
bool _isEditing = false;
bool _isPreviewMode = false;
bool _isLoading = true;
@ -154,6 +157,19 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
if (await _onWillPop() && mounted) Navigator.pop(context);
}
/// Holding the D-pad center button goes back to the list.
KeyEventResult _onCenterKey(FocusNode node, KeyEvent event) {
if (event.logicalKey != LogicalKeyboardKey.select) {
return KeyEventResult.ignored;
}
if (event is KeyDownEvent) {
_centerHold = Timer(const Duration(milliseconds: 500), _backToList);
} else if (event is KeyUpEvent) {
_centerHold?.cancel();
}
return KeyEventResult.handled;
}
Future<void> _quit() async {
if (await _onWillPop()) await widget.onQuit();
}
@ -193,6 +209,11 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
primary(LogicalKeyboardKey.numpadSubtract): () => _stepFontSize(-2),
primary(LogicalKeyboardKey.digit0): () => _updateFontSize(16),
// In edit mode the arrows move the caret instead.
if ((_isPreviewMode || !_isEditing) && dpadNavigation) ...{
const SingleActivator(LogicalKeyboardKey.arrowLeft): _backToList,
const SingleActivator(LogicalKeyboardKey.arrowRight): () =>
openMenuWithFocus(_menuKey),
},
if (_isPreviewMode || !_isEditing) ...{
const SingleActivator(LogicalKeyboardKey.arrowDown): () => _scroll(3),
const SingleActivator(LogicalKeyboardKey.arrowUp): () => _scroll(-3),
@ -273,6 +294,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
_screenFocus.dispose();
_sourceScroll.dispose();
_previewScroll.dispose();
_centerHold?.cancel();
super.dispose();
}
@ -287,6 +309,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
child: Focus(
focusNode: _screenFocus,
autofocus: true,
onKeyEvent: _onCenterKey,
child: Scaffold(
appBar: AppBar(
title: Text(widget.note.uri.pathSegments.last.replaceAll('.md', '')),
@ -307,6 +330,7 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: PopupMenuButton<String>(
key: _menuKey,
icon: SvgPicture.asset(
"assets/blaster.svg",
width: 30,
@ -364,18 +388,26 @@ class _NoteEditorScreenState extends State<NoteEditorScreen> {
Text('Font Size'),
StatefulBuilder(
builder: (context, setState) {
return Slider(
value: _fontSize,
min: 12.0,
max: 24.0,
divisions: 6,
label: _fontSize.toString(),
onChanged: (newSize) {
setState(() {
_fontSize = newSize;
});
_updateFontSize(newSize);
},
// Directional mode leaves Up/Down to the D-pad, so
// focus can move past the slider; Left/Right adjust it.
return MediaQuery(
data: MediaQuery.of(context).copyWith(
navigationMode: dpadNavigation
? NavigationMode.directional
: NavigationMode.traditional),
child: Slider(
value: _fontSize,
min: 12.0,
max: 24.0,
divisions: 6,
label: _fontSize.toString(),
onChanged: (newSize) {
setState(() {
_fontSize = newSize;
});
_updateFontSize(newSize);
},
),
);
},
),