feat: Properly recognize the current input method

This commit is contained in:
PartyDonut 2025-10-23 15:58:18 +02:00
parent 561351dd09
commit 8b9bc04380
7 changed files with 154 additions and 36 deletions

View file

@ -9,6 +9,7 @@ import 'package:fladder/providers/settings/home_settings_provider.dart';
import 'package:fladder/screens/home_screen.dart'; import 'package:fladder/screens/home_screen.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout_model.dart'; import 'package:fladder/util/adaptive_layout/adaptive_layout_model.dart';
import 'package:fladder/util/debug_banner.dart'; import 'package:fladder/util/debug_banner.dart';
import 'package:fladder/util/input_detector.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/poster_defaults.dart'; import 'package:fladder/util/poster_defaults.dart';
import 'package:fladder/util/resolution_checker.dart'; import 'package:fladder/util/resolution_checker.dart';
@ -188,11 +189,6 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
final selectedViewSize = selectAvailableOrSmaller<ViewSize>(viewSize, acceptedViewSizes, ViewSize.values); final selectedViewSize = selectAvailableOrSmaller<ViewSize>(viewSize, acceptedViewSizes, ViewSize.values);
final selectedLayoutMode = selectAvailableOrSmaller<LayoutMode>(layoutMode, acceptedLayouts, LayoutMode.values); final selectedLayoutMode = selectAvailableOrSmaller<LayoutMode>(layoutMode, acceptedLayouts, LayoutMode.values);
final input = htpcMode
? InputDevice.dPad
: (isDesktop || kIsWeb)
? InputDevice.pointer
: InputDevice.touch;
final posterDefaults = const PosterDefaults(size: 350, ratio: 0.55); final posterDefaults = const PosterDefaults(size: 350, ratio: 0.55);
@ -200,7 +196,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
AdaptiveLayoutModel( AdaptiveLayoutModel(
viewSize: selectedViewSize, viewSize: selectedViewSize,
layoutMode: selectedLayoutMode, layoutMode: selectedLayoutMode,
inputDevice: input, inputDevice: InputDevice.pointer,
platform: currentPlatform, platform: currentPlatform,
isDesktop: isDesktop, isDesktop: isDesktop,
sideBarWidth: 0, sideBarWidth: 0,
@ -213,8 +209,12 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: isKeyboardOpen, valueListenable: isKeyboardOpen,
builder: (context, value, child) { builder: (context, value, child) {
return MediaQuery( return InputDetector(
isDesktop: isDesktop,
htpcMode: htpcMode,
child: (input) => MediaQuery(
data: mediaQuery.copyWith( data: mediaQuery.copyWith(
navigationMode: input == InputDevice.dPad ? NavigationMode.directional : NavigationMode.traditional,
padding: (isDesktop || kIsWeb padding: (isDesktop || kIsWeb
? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16) ? const EdgeInsets.only(top: defaultTitleBarHeight, bottom: 16)
: mediaQuery.padding), : mediaQuery.padding),
@ -242,6 +242,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
: widget.child(context), : widget.child(context),
), ),
), ),
),
); );
}, },
); );

View file

@ -86,7 +86,10 @@ class AdaptiveLayoutModel {
@override @override
bool operator ==(covariant AdaptiveLayoutModel other) { bool operator ==(covariant AdaptiveLayoutModel other) {
if (identical(this, other)) return true; if (identical(this, other)) return true;
return other.viewSize == viewSize && other.layoutMode == layoutMode && other.sideBarWidth == sideBarWidth; return other.viewSize == viewSize &&
other.layoutMode == layoutMode &&
other.sideBarWidth == sideBarWidth &&
other.inputDevice == inputDevice;
} }
@override @override

View file

@ -146,6 +146,8 @@ class FocusButtonState extends State<FocusButton> {
child: Focus( child: Focus(
focusNode: focusNode, focusNode: focusNode,
autofocus: widget.autoFocus, autofocus: widget.autoFocus,
canRequestFocus: widget.onTap != null || widget.onLongPress != null || widget.onSecondaryTapDown != null,
skipTraversal: widget.onTap == null && widget.onLongPress == null && widget.onSecondaryTapDown != null,
onFocusChange: (value) { onFocusChange: (value) {
widget.onFocusChanged?.call(value); widget.onFocusChanged?.call(value);
if (value) { if (value) {

View file

@ -0,0 +1,106 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
class InputDetector extends StatefulWidget {
final bool isDesktop;
final bool htpcMode;
final Widget Function(InputDevice input) child;
const InputDetector({
super.key,
required this.isDesktop,
required this.htpcMode,
required this.child,
});
@override
State<InputDetector> createState() => _InputDetectorState();
}
class _InputDetectorState extends State<InputDetector> {
late InputDevice _currentInput = widget.htpcMode
? InputDevice.dPad
: (widget.isDesktop || kIsWeb)
? InputDevice.pointer
: InputDevice.touch;
@override
void initState() {
super.initState();
_startListeningToKeyboard();
}
void _startListeningToKeyboard() {
ServicesBinding.instance.keyboard.addHandler(_handleKeyPress);
}
@override
void dispose() {
ServicesBinding.instance.keyboard.removeHandler(_handleKeyPress);
super.dispose();
}
bool _handleKeyPress(KeyEvent event) {
if (event is KeyDownEvent) {
if (_isEditableTextFocused() &&
(event.logicalKey == LogicalKeyboardKey.arrowUp ||
event.logicalKey == LogicalKeyboardKey.arrowDown ||
event.logicalKey == LogicalKeyboardKey.arrowLeft ||
event.logicalKey == LogicalKeyboardKey.arrowRight)) {
return false;
}
if (event.logicalKey == LogicalKeyboardKey.arrowUp ||
event.logicalKey == LogicalKeyboardKey.arrowDown ||
event.logicalKey == LogicalKeyboardKey.arrowLeft ||
event.logicalKey == LogicalKeyboardKey.arrowRight ||
event.logicalKey == LogicalKeyboardKey.select) {
_updateInputDevice(InputDevice.dPad);
}
}
return false;
}
bool _isEditableTextFocused() {
final focus = FocusManager.instance.primaryFocus;
if (focus == null) return false;
final ctx = focus.context;
if (ctx == null) return false;
if (ctx.widget is EditableText) return true;
return ctx.findAncestorWidgetOfExactType<EditableText>() != null;
}
void _handlePointerEvent(PointerEvent event) {
if (event is PointerDownEvent) {
if (event.kind == PointerDeviceKind.touch) {
_updateInputDevice(InputDevice.touch);
} else if (event.kind == PointerDeviceKind.mouse) {
_updateInputDevice(InputDevice.pointer);
}
}
}
void _updateInputDevice(InputDevice device) {
if (_currentInput != device) {
setState(() {
_currentInput = device;
});
}
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: _handlePointerEvent,
behavior: HitTestBehavior.translucent,
child: Builder(
builder: (context) => widget.child(_currentInput),
),
);
}
}

View file

@ -19,7 +19,6 @@ class MediaQueryScaler extends StatelessWidget {
final screenSize = MediaQuery.sizeOf(context) * scale; final screenSize = MediaQuery.sizeOf(context) * scale;
final scaledMedia = mediaQuery.copyWith( final scaledMedia = mediaQuery.copyWith(
navigationMode: NavigationMode.directional,
size: screenSize, size: screenSize,
padding: mediaQuery.padding * scale, padding: mediaQuery.padding * scale,
viewInsets: mediaQuery.viewInsets * scale, viewInsets: mediaQuery.viewInsets * scale,

View file

@ -10,7 +10,11 @@ class EnumBox<T> extends StatelessWidget {
final String current; final String current;
final List<ItemAction> Function(BuildContext context) itemBuilder; final List<ItemAction> Function(BuildContext context) itemBuilder;
const EnumBox({required this.current, required this.itemBuilder, super.key}); const EnumBox({
required this.current,
required this.itemBuilder,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/theme.dart'; import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/fladder_image.dart';
Future<void> showBottomSheetPill({ Future<void> showBottomSheetPill({
@ -44,7 +45,9 @@ Future<void> showBottomSheetPill({
height: 8, height: 8,
width: 35, width: 35,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSurface, color: AdaptiveLayout.inputDeviceOf(context) == InputDevice.touch
? Theme.of(context).colorScheme.onSurface
: Colors.transparent,
borderRadius: FladderTheme.largeShape.borderRadius, borderRadius: FladderTheme.largeShape.borderRadius,
), ),
), ),