mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-19 20:26:32 -07:00
fix: Improve keyboard input handling (#102)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
2038847552
commit
76ac1aaa5b
7 changed files with 584 additions and 571 deletions
|
|
@ -16,6 +16,7 @@ import 'package:fladder/screens/book_viewer/book_viewer_settings.dart';
|
|||
import 'package:fladder/screens/shared/default_titlebar.dart';
|
||||
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/input_handler.dart';
|
||||
import 'package:fladder/util/throttler.dart';
|
||||
import 'package:fladder/widgets/shared/fladder_slider.dart';
|
||||
|
||||
|
|
@ -75,12 +76,10 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
|||
viewController.visibilityChanged.addListener(() {
|
||||
toggleControls(value: viewController.controlsVisible);
|
||||
});
|
||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
||||
WakelockPlus.disable();
|
||||
ScreenBrightness().resetScreenBrightness();
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||
|
|
@ -134,6 +133,8 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
|||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
child: InputHandler(
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: Stack(
|
||||
children: [
|
||||
IgnorePointer(
|
||||
|
|
@ -359,6 +360,7 @@ class _BookViewerControlsState extends ConsumerState<BookViewerControls> {
|
|||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fladder/providers/user_provider.dart';
|
|||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/screens/shared/input_fields.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/input_handler.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/throttler.dart';
|
||||
|
|
@ -99,7 +100,7 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
timerController.playPause();
|
||||
return true;
|
||||
}
|
||||
if (value.logicalKey == LogicalKeyboardKey.keyF) {
|
||||
if (value.logicalKey == LogicalKeyboardKey.space) {
|
||||
widget.toggleOverlay?.call(null);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -117,12 +118,10 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
timerController.reset();
|
||||
},
|
||||
);
|
||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMinimize() {
|
||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
||||
timerController.cancel();
|
||||
super.onWindowMinimize();
|
||||
}
|
||||
|
|
@ -145,9 +144,9 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
|
||||
final padding = MediaQuery.of(context).padding;
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
await WakelockPlus.disable();
|
||||
},
|
||||
onPopInvokedWithResult: (didPop, result) async => await WakelockPlus.disable(),
|
||||
child: InputHandler(
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: Stack(
|
||||
children: [
|
||||
Align(
|
||||
|
|
@ -217,8 +216,10 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
children: [
|
||||
Text(
|
||||
"${widget.currentIndex + 1} / ${widget.loadingMoreItems ? "-" : "${widget.itemCount}"} ",
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
if (widget.loadingMoreItems)
|
||||
const SizedBox.square(
|
||||
|
|
@ -254,10 +255,11 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
),
|
||||
const SizedBox(height: 5),
|
||||
IntInputField(
|
||||
controller:
|
||||
TextEditingController(text: (widget.currentIndex + 1).toString()),
|
||||
controller: TextEditingController(
|
||||
text: (widget.currentIndex + 1).toString()),
|
||||
onSubmitted: (value) {
|
||||
final position = ((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
|
||||
final position =
|
||||
((value ?? 0) - 1).clamp(0, widget.itemCount - 1);
|
||||
widget.pageController.jumpToPage(position);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
|
@ -299,7 +301,8 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
||||
padding:
|
||||
const EdgeInsets.all(8.0).add(EdgeInsets.only(left: padding.left, right: padding.right)),
|
||||
child: Row(
|
||||
children: [
|
||||
ElevatedIconButton(
|
||||
|
|
@ -332,6 +335,7 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
import 'package:fladder/util/input_handler.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
|
||||
class PassCodeInput extends ConsumerStatefulWidget {
|
||||
|
|
@ -19,18 +20,6 @@ class _PassCodeInputState extends ConsumerState<PassCodeInput> {
|
|||
final passCodeLength = 4;
|
||||
var currentPasscode = "";
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _onKey(KeyEvent value) {
|
||||
if (value is KeyDownEvent) {
|
||||
final keyInt = int.tryParse(value.logicalKey.keyLabel);
|
||||
|
|
@ -48,7 +37,9 @@ class _PassCodeInputState extends ConsumerState<PassCodeInput> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
return InputHandler(
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: AlertDialog(
|
||||
scrollable: true,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
|
@ -103,6 +94,7 @@ class _PassCodeInputState extends ConsumerState<PassCodeInput> {
|
|||
)
|
||||
].addPadding(const EdgeInsets.symmetric(vertical: 8)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:async/async.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/util/input_handler.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
||||
class VideoPlayerSeekIndicator extends ConsumerStatefulWidget {
|
||||
|
|
@ -20,18 +21,6 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
|||
bool visible = false;
|
||||
int seekPosition = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void onSeekEnd() {
|
||||
setState(() {
|
||||
visible = false;
|
||||
|
|
@ -46,7 +35,7 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
|||
|
||||
void onSeekStart(int value) {
|
||||
if (timer == null) {
|
||||
timer = RestartableTimer(const Duration(seconds: 2), () => onSeekEnd());
|
||||
timer = RestartableTimer(const Duration(milliseconds: 500), () => onSeekEnd());
|
||||
setState(() {
|
||||
seekPosition = 0;
|
||||
});
|
||||
|
|
@ -85,7 +74,10 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
return InputHandler(
|
||||
autoFocus: true,
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: IgnorePointer(
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: (visible && seekPosition != 0) ? 1 : 0,
|
||||
|
|
@ -112,6 +104,7 @@ class _VideoPlayerSeekIndicatorState extends ConsumerState<VideoPlayerSeekIndica
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import 'package:fladder/models/settings/subtitle_settings_model.dart';
|
|||
|
||||
class VideoSubtitles extends ConsumerStatefulWidget {
|
||||
final VideoController controller;
|
||||
final bool overlayed;
|
||||
final bool overLayed;
|
||||
const VideoSubtitles({
|
||||
required this.controller,
|
||||
this.overlayed = false,
|
||||
this.overLayed = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class _VideoSubtitlesState extends ConsumerState<VideoSubtitles> {
|
|||
return SubtitleText(
|
||||
subModel: settings,
|
||||
padding: padding,
|
||||
offset: (widget.overlayed ? 0.5 : settings.verticalOffset),
|
||||
offset: (widget.overLayed ? 0.5 : settings.verticalOffset),
|
||||
text: text,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import 'package:fladder/screens/video_player/components/video_subtitles.dart';
|
|||
import 'package:fladder/screens/video_player/components/video_volume_slider.dart';
|
||||
import 'package:fladder/util/adaptive_layout.dart';
|
||||
import 'package:fladder/util/duration_extensions.dart';
|
||||
import 'package:fladder/util/input_handler.dart';
|
||||
import 'package:fladder/util/list_padding.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
|
@ -49,7 +50,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
);
|
||||
|
||||
final fadeDuration = const Duration(milliseconds: 350);
|
||||
final focusNode = FocusNode();
|
||||
bool showOverlay = true;
|
||||
bool wasPlaying = false;
|
||||
|
||||
|
|
@ -79,7 +79,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
} else if (showCreditSkipButton) {
|
||||
skipCredits(introSkipModel);
|
||||
}
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
if (value.logicalKey == LogicalKeyboardKey.escape) {
|
||||
disableFullscreen();
|
||||
|
|
@ -103,27 +102,14 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
ServicesBinding.instance.keyboard.addHandler(_onKey);
|
||||
timer.reset();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ServicesBinding.instance.keyboard.removeHandler(_onKey);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final introSkipModel = ref.watch(playBackModel.select((value) => value?.introSkipModel));
|
||||
final player = ref.watch(videoPlayerProvider.select((value) => value.controller));
|
||||
if (AdaptiveLayout.of(context).isDesktop) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
return Listener(
|
||||
return InputHandler(
|
||||
autoFocus: false,
|
||||
onKeyEvent: (node, event) => _onKey(event) ? KeyEventResult.handled : KeyEventResult.ignored,
|
||||
child: Listener(
|
||||
onPointerSignal: (event) => resetTimer(),
|
||||
child: PopScope(
|
||||
canPop: false,
|
||||
|
|
@ -145,7 +131,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
VideoSubtitles(
|
||||
key: const Key('subtitles'),
|
||||
controller: player,
|
||||
overlayed: showOverlay,
|
||||
overLayed: showOverlay,
|
||||
),
|
||||
if (AdaptiveLayout.of(context).isDesktop)
|
||||
Consumer(builder: (context, ref, child) {
|
||||
|
|
@ -206,6 +192,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
35
lib/util/input_handler.dart
Normal file
35
lib/util/input_handler.dart
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class InputHandler extends StatefulWidget {
|
||||
final bool autoFocus;
|
||||
final KeyEventResult Function(FocusNode node, KeyEvent event)? onKeyEvent;
|
||||
final Widget child;
|
||||
const InputHandler({
|
||||
required this.child,
|
||||
this.autoFocus = true,
|
||||
this.onKeyEvent,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<InputHandler> createState() => _InputHandlerState();
|
||||
}
|
||||
|
||||
class _InputHandlerState extends State<InputHandler> {
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
autofocus: widget.autoFocus,
|
||||
focusNode: focusNode,
|
||||
onFocusChange: (value) {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
},
|
||||
onKeyEvent: widget.onKeyEvent,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue